package p.ithorns.tools.excel;

import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.converters.ConverterKeyBuild;
import cn.idev.excel.enums.CellDataTypeEnum;
import cn.idev.excel.exception.ExcelDataConvertException;
import cn.idev.excel.metadata.Head;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import cn.idev.excel.read.metadata.holder.ReadSheetHolder;
import cn.idev.excel.read.metadata.property.ExcelReadHeadProperty;
import cn.idev.excel.support.cglib.beans.BeanMap;
import cn.idev.excel.util.BeanMapUtils;
import cn.idev.excel.util.ClassUtils;
import cn.idev.excel.util.ConverterUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import p.ithorns.tools.excel.model.FieldInfo;
import p.ithorns.tools.excel.model.RowError;
import p.ithorns.tools.excel.model.SheetListDataBean;
import p.ithorns.tools.excel.read.ExcelHead;

import javax.validation.metadata.ConstraintDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * AssistUtil
 *
 * @author HtL
 * @date 2024/7/30 16:02
 * @since 1.0.0
 */
public final class AssistTools {

    private final static Logger log = LoggerFactory.getLogger(AssistTools.class);

    /**
     * 构建Sheet表数据
     *
     * @param sheet     -表名称
     * @param clazz     -数据类型
     * @param rowErrors -异常数据
     * @param <T>       -具体数据类型
     * @return SheetDataBean
     */
    public static <T> SheetListDataBean makeErrSheetBean(String sheet,
                                                         Class<?> clazz,
                                                         List<? extends RowError<?>> rowErrors) {
        Map<String, FieldInfo> fieldInfoMap = resolveExcelFieldMap(clazz);

        List<FieldInfo> fieldList = fieldInfoMap.values()
                .stream()
                .sorted(Comparator.comparingInt(FieldInfo::getIndex))
                .collect(Collectors.toList());

        // head
        List<List<String>> headNames = makeErrHeadNames(fieldList);
        // row
        List<List<Object>> dataList = makeErrRowValues(fieldList, rowErrors);

        return SheetListDataBean.builder()
                .sheetName(sheet)
                .headNames(headNames)
                .dataList(dataList)
                .build();
    }


    public static <T> List<List<String>> makeHeadNames(Class<T> clazz) {
        Map<String, FieldInfo> fieldInfoMap = resolveExcelFieldMap(clazz);
        return makeHeadNames(fieldInfoMap.values());
    }



    /**
     * 头名称
     *
     * @param fieldList -
     * @return -
     */
    public static List<List<String>> makeHeadNames(final Collection<FieldInfo> fieldList) {
        return fieldList.stream()
                // 先按index排序
                .sorted(Comparator.comparingInt(FieldInfo::getIndex))
                .map(fi -> {
                    List<String> headNames = new ArrayList<>(2);

                    String alias = fi.getMainAlias();
                    headNames.add(alias);

                    String constraintMsg = fi.getConstraintMsg();
                   headNames.add(constraintMsg);
                    // 固定就是 [名称头-约束头] 不存在其他顺序
                    return headNames;
                })
                .collect(Collectors.toList());
    }

    /**
     * 根据字段表创建头数据列表
     *
     * @param fieldList -字段表
     * @return List<List < String>>
     */
    public static List<List<String>> makeErrHeadNames(final Collection<FieldInfo> fieldList) {
        List<List<String>> names = makeHeadNames(fieldList);
        names.add(Collections.singletonList("错误信息"));
        return names;
    }


    /**
     * 组装异常行数据
     *
     * @param fields    - 字段
     * @param rowErrors - 异常数
     * @return List<List < Object>>
     */
    private static List<List<Object>> makeErrRowValues(final Collection<FieldInfo> fields,
                                                       final List<? extends RowError<?>> rowErrors) {
        List<List<Object>> values = new ArrayList<>(rowErrors.size());
        for (RowError<?> error : rowErrors) {
            Object rowData = error.getRowData();
            if (null != rowData) {
                List<Object> row = new ArrayList<>(fields.size());
                for (FieldInfo fi : fields) {
                    Field field = fi.getField();
                    Object value = null;
                    try {
                        field.setAccessible(true);
                        value = field.get(rowData);
                    } catch (IllegalAccessException e) {
                        log.error(e.getMessage());
                    }
                    row.add(value);
                }
                // 增加错误信息
                row.add(error.getMsg());
                values.add(row);
            }
        }
        return values;
    }


    /**
     * 解析字段信息映射表
     *
     * @param clazz -类型
     * @return Map<字段名, 字段信息>
     */
    public static Map<String, FieldInfo> resolveExcelFieldMap(Class<?> clazz) {
        Map<String, Set<ConstraintDescriptor<?>>> constraintMap = Validators.getConstraintMap(clazz);
        final Map<String, FieldInfo> result = new HashMap<>(16);
        Field[] fields = clazz.getDeclaredFields();
        ExcelHead classHead = clazz.getAnnotation(ExcelHead.class);
        for (Field value : fields) {
            String fieldName = value.getName();
            Field field = null;
            try {
                field = clazz.getDeclaredField(fieldName);
            } catch (NoSuchFieldException e) {
                log.error(e.getMessage());
                continue;
            }
            field.setAccessible(true);
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);

            if (excelProperty != null) {
                int index = excelProperty.index();
                String[] aliases = excelProperty.value();

                ExcelHead fieldHead = field.getAnnotation(ExcelHead.class);
                int headRow = fieldHead != null ? fieldHead.value() : classHead != null ? classHead.value() : 0;

                Set<ConstraintDescriptor<?>> constraints = constraintMap.get(fieldName);
                FieldInfo fieldInfo = new FieldInfo(index, fieldName, aliases, headRow, field);
                fieldInfo.setConstraints(constraints);
                result.put(fieldName,fieldInfo);
            }
        }

        return result;
    }


    /**
     * 转换行数据到Java对象
     *
     * @param cellDataMap     -行数据
     * @param readSheetHolder -表信息
     * @param context         - 上下文
     * @return Object
     */
    public static Object convertRowToXlsBean(Map<Integer, ReadCellData<?>> cellDataMap,
                                             ReadSheetHolder readSheetHolder,
                                             AnalysisContext context) {
        ExcelReadHeadProperty excelReadHeadProperty = readSheetHolder.excelReadHeadProperty();

        // 行序号
        Integer rowIndex = context.readRowHolder().getRowIndex();
        // 头数据类型
        Class<?> headClazz = readSheetHolder.excelReadHeadProperty().getHeadClazz();

        Object bean;
        try {
            bean = excelReadHeadProperty.getHeadClazz().getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new ExcelDataConvertException(rowIndex, 0, new ReadCellData<>(CellDataTypeEnum.EMPTY), null,
                    "Can not instance class: " + headClazz.getName(), e);
        }

        Map<Integer, Head> headMap = excelReadHeadProperty.getHeadMap();
        BeanMap dataMap = BeanMapUtils.create(bean);
        for (Map.Entry<Integer, Head> entry : headMap.entrySet()) {
            Integer index = entry.getKey();
            if (!cellDataMap.containsKey(index)) {
                continue;
            }

            ReadCellData<?> cellData = cellDataMap.get(index);
            Head head = entry.getValue();
            String fieldName = head.getFieldName();
            Field field = head.getField();

            Map<ConverterKeyBuild.ConverterKey, Converter<?>> converterMap = readSheetHolder.converterMap();
            ExcelContentProperty property = ClassUtils.declaredExcelContentProperty(dataMap, headClazz, fieldName, readSheetHolder);
            Object value = ConverterUtils.convertToJavaObject(
                    cellData, field, property, converterMap, context, rowIndex, index);
            if (value != null) {
                dataMap.put(fieldName, value);
            }
        }

        return bean;
    }

    /**
     * 从File创建MultipartFile
     *
     * @param file  -文件
     * @param field -form field
     * @return MultipartFile
     */
    public static MultipartFile createMultipartFile(File file, String field) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        FileItem item = factory.createItem(field,
                MediaType.MULTIPART_FORM_DATA_VALUE, true, file.getName());
        int bytesRead;
        byte[] buffer = new byte[8192];
        try (FileInputStream fis = new FileInputStream(file);
             OutputStream os = item.getOutputStream()) {
            while ((bytesRead = fis.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new CommonsMultipartFile(item);
    }

}